package nachos.network;

import nachos.machine.*;

/**
 * A mail message. Includes a packet header, a mail header, and the actual
 * payload.
 *
 * @see	nachos.machine.Packet
 */
public class NachosPacket {
    /**
     * Allocate a nachos packet to be sent, using the specified parameters.
     *
     * @param	dstLink		the destination link address.
     * @param	dstPort		the destination port.
     * @param	srcLink		the source link address.
     * @param	srcPort		the source port.
     * @param	contents	the contents of the packet.
     * @param 	seqNo		the sequence number for this packet
     * @param	flag		the header flag
     */
    public NachosPacket(int dstLink, int dstPort, int srcLink, int srcPort,
		       byte[] contents, int seqNo, byte flag) throws MalformedPacketException {
	// make sure the paramters are valid
	if (dstPort < 0 || dstPort >= portLimit ||
	    srcPort < 0 || srcPort >= portLimit ||
	    contents.length > maxContentsLength)
	    throw new MalformedPacketException();

	this.flag = flag;
	switch(flag){
	case 0:
		break; //it is a data packet, no flags are set
	case FIN:
		this.fin = true;
		break;
	case STP:
		this.stp = true;
		break;
	case ACK:
		this.ack = true;
		break;
	case SYN:
		this.syn = true;
		break;
	case SYN_ACK:
		this.syn_ack = true;
		break;
	case FIN_ACK:
		this.fin_ack = true;
		break;
		default:
			throw new MalformedPacketException();
	}
	
	this.dstPort = (byte) dstPort;
	this.srcPort = (byte) srcPort;
	this.contents = contents;
	this.seqNo = seqNo;

	byte[] packetContents = new byte[headerLength + contents.length];

	packetContents[0] = (byte) dstPort;
	packetContents[1] = (byte) srcPort;
	//the sequence number starts at bit 32 of the header, which is 4th byte:	
	packetContents[2] = 0;
	packetContents[3] = flag; 
	//now get the bytes out of the integer sequence number and put them in the
	// 4 bytes of the header, starting at location 4:
	packetContents[4] = (byte) (seqNo >>> 24);
	packetContents[5] = (byte) (seqNo >>> 16);
	packetContents[6] = (byte) (seqNo >>> 8);
	packetContents[7] = (byte) (seqNo);

	System.arraycopy(contents, 0, packetContents, headerLength,
			 contents.length);

	this.packet = new Packet(dstLink, srcLink, packetContents);
    }
    
    /**
     * Allocate a new mail message to be sent, using the specified parameters.
     *
     * @param	dstLink		the destination link address.
     * @param	dstPort		the destination port.
     * @param	srcLink		the source link address.
     * @param	srcPort		the source port.
     * @param	contents	the contents of the packet.
     * @param 	seqNo		the sequence number
     */
    public NachosPacket(int dstLink, int dstPort, int srcLink, int srcPort,
		       byte[] contents, int seqNo) throws MalformedPacketException {
	// make sure the paramters are valid
	if (dstPort < 0 || dstPort >= portLimit ||
	    srcPort < 0 || srcPort >= portLimit ||
	    contents.length > maxContentsLength)
	    throw new MalformedPacketException();

	this.flag = 0;
		
	this.dstPort = (byte) dstPort;
	this.srcPort = (byte) srcPort;
	this.contents = contents;
	this.seqNo = seqNo;

	byte[] packetContents = new byte[headerLength + contents.length];

	packetContents[0] = (byte) dstPort;
	packetContents[1] = (byte) srcPort;
	//the sequence number starts at bit 32 of the header, which is 4th byte:	
	packetContents[2] = 0;
	packetContents[3] = 0;
	
	//now get the bytes out of the integer sequence number and put them int the
	// 4 bytes of the header, starting at location 4:
	packetContents[4] = (byte) (seqNo >>> 24);
	packetContents[5] = (byte) (seqNo >>> 16);
	packetContents[6] = (byte) (seqNo >>> 8);
	packetContents[7] = (byte) (seqNo);

	System.arraycopy(contents, 0, packetContents, headerLength,
			 contents.length);

	this.packet = new Packet(dstLink, srcLink, packetContents);
    }
    /**
     * Allocate a new mail message to be sent, using the specified parameters.
     *
     * @param	dstLink		the destination link address.
     * @param	dstPort		the destination port.
     * @param	srcLink		the source link address.
     * @param	srcPort		the source port.
     * @param	contents	the contents of the packet.
     * @param 	seqNo		the sequence number
     */
    public NachosPacket(int dstLink, int dstPort, int srcLink, int srcPort,
		       byte[] contents) throws MalformedPacketException {
	// make sure the paramters are valid
	if (dstPort < 0 || dstPort >= portLimit ||
	    srcPort < 0 || srcPort >= portLimit ||
	    contents.length > maxContentsLength)
	    throw new MalformedPacketException();

	this.flag = 0;
		
	this.dstPort = (byte) dstPort;
	this.srcPort = (byte) srcPort;
	this.contents = contents;
	this.seqNo = -1;

	byte[] packetContents = new byte[headerLength + contents.length];

	packetContents[0] = (byte) dstPort;
	packetContents[1] = (byte) srcPort;
	//the sequence number starts at bit 32 of the header, which is 4th byte:	
	packetContents[2] = 0;
	packetContents[3] = 0;
	
	//now get the bytes out of the integer sequence number and put them int the
	// 4 bytes of the header, starting at location 4:
	packetContents[4] = (byte) (seqNo >>> 24);
	packetContents[5] = (byte) (seqNo >>> 16);
	packetContents[6] = (byte) (seqNo >>> 8);
	packetContents[7] = (byte) (seqNo);

	System.arraycopy(contents, 0, packetContents, headerLength,
			 contents.length);

	this.packet = new Packet(dstLink, srcLink, packetContents);
    }
	
    /**
     * Allocate a nachos packet using the specified packet from the network.
     *
     * @param	packet	the packet containg the mail message.
     */
    public NachosPacket (Packet packet) throws MalformedPacketException {
	this.packet = packet;
	
	// make sure we have a valid header
	if (packet.contents.length < headerLength ||
	    packet.contents[0] < 0 || packet.contents[0] >= portLimit ||
	    packet.contents[1] < 0 || packet.contents[1] >= portLimit ||
	    packet.contents[2] != 0) 
	    throw new MalformedPacketException();

	this.flag = packet.contents[3];
	switch(flag){
	case 0:
		break; //it is a data packet, no flags are set
	case FIN:
		this.fin = true;
		break;
	case STP:
		this.stp = true;
		break;
	case ACK:
		this.ack = true;
		break;
	case SYN:
		this.syn = true;
		break;
	case SYN_ACK:
		this.syn_ack = true;
		break;
	case FIN_ACK:
		this.fin_ack = true;
		break;
		default:
			throw new MalformedPacketException();
	}
	
	dstPort = packet.contents[0];
	srcPort = packet.contents[1];
	this.seqNo = ((packet.contents[4] & 0xff) << 24) | ((packet.contents[5] & 0xff) << 16) 
			| ((packet.contents[6] & 0xff) << 8) | (packet.contents[7] & 0xff);
	contents = new byte[packet.contents.length - headerLength];
	System.arraycopy(packet.contents, headerLength, contents, 0,
			 contents.length);
    }

    /**
     * Return a string representation of the message headers.
     */
    public String toString() {
    	String strFlag = "data";
    	switch(flag){
    	case FIN:
    		strFlag = "FIN";
    		break;
    	case STP:
    		strFlag = "STP";
    		break;
    	case ACK:
    		strFlag = "ACK";
    		break;
    	case SYN:
    		strFlag = "SYN";
    		break;
    	case SYN_ACK:
    		strFlag = "SYN_ACK";
    		break;
    	case FIN_ACK:
    		strFlag = "FIN_ACK";
    	}
    	return "from (" + packet.srcLink + ":" + srcPort +
	    ") to (" + packet.dstLink + ":" + dstPort +
	    "), flag: " + strFlag + ", seqNo: " + this.seqNo + ", " +   
	    contents.length + " bytes";
    }
    
    /** This nachos packet, as a network packet that can be sent through a network link. */
    public Packet packet;
    /** The port used by this message on the destination machine. */
    public int dstPort;
    /** The port used by this message on the source machine. */
    public int srcPort;
    /** The contents of this message, excluding the mail message header. */
    public byte[] contents;
    /** The sequence number of this packet	*/
    public int seqNo;
    public byte flag;
  
    /**
     * The number of bytes in a  header. The header is formatted as
     * follows:
     *
     * <table>
     * <tr><td>offset</td><td>size</td><td>value</td></tr>
     * <tr><td>0</td><td>1</td><td>destination port</td></tr>
     * <tr><td>1</td><td>1</td><td>source port</td></tr>
     * <tr><td>2</td><td>2</td><td>Must Be Zero + FIN/STP/ACK/SYN flags</td></tr>
     * <tr><td>4</td><td>4</td><td>SequenceNumber</td></tr>
     * FIN flag is bit 28
     * STP flag is bit 29
     * ACK flag is bit 30
     * SYN flag is bit 31
     * </table>
     */
    public static final int headerLength = 8;

    /** Maximum payload (real data) that can be included in a single mesage. */
    public static final int maxContentsLength =
	Packet.maxContentsLength - headerLength;

    
    /**
     * The upper limit on mail ports. All ports fall between <tt>0</tt> and
     * <tt>portLimit - 1</tt>.
     */    
    public static final int portLimit = 128;
    
    /**
     * The header flags
     */
    public boolean fin;
    public boolean stp;
    public boolean ack;
    public boolean syn;
    public boolean syn_ack;
    public boolean fin_ack;
       
    
    /**
     * Constants for the header flags
     */
    public static final byte FIN = 8;
    public static final byte STP = 4;
    public static final byte ACK = 2;
    public static final byte SYN = 1;
    public static final byte SYN_ACK = 3;
    public static final byte FIN_ACK = 10;
    public static final byte DATA = 0;
}
